﻿using System;
using System.Collections.Generic;
using System.Linq;
using Monopoly.Exceptions;

namespace Monopoly.Domain
{
    public class Game
    {
        private Bank Bank;
        private Gameboard Gameboard;
        private Dice Dice1;
        private Dice Dice2;
        private IList<Player> Players;
        private Player CurrentPlayer;
        private short DoublesInRow;
        private Random Random;

        /// <summary>
        /// Create a new Gameboard, with a new Bank, a new List of dices, and new Players
        /// </summary>
        public Game()
        {
            this.Gameboard = new Gameboard();
            this.Bank = new Bank(this.Gameboard.GetPropertyList());
            this.Random = new Random();
            this.Dice1 = new Dice(Random);
            this.Dice2 = new Dice(Random);
            this.Players = new List<Player>();

            ReinitializeIdentity();
        }

        /// <summary>
        /// Reinitialize identity values
        /// </summary>
        private void ReinitializeIdentity()
        {
            Box.ReinitializeIdentity();
            Card.ReinitializeIdentity();
            RealEstate.ReinitializeIdentity();
            Dice.ReinitializeIdentity();
        }

        /// <summary>
        /// Add a player into the game if there is any piece left in the gameboard. The player's piece is randomly affected
        /// </summary>
        /// <param name="PlayerName">The name of the player</param>
        /// <returns><code>true</code> if the player has been successfuly added, otherwise <code>false</code></returns>
        public bool AddPlayer(string PlayerName)
        {
            if (Players.Where(p => p.GetName() == PlayerName).Count() == 0)
            {
                Piece Piece = this.Gameboard.PopAvailablePiece();
                if (Piece != null)
                {
                    this.Players.Add(new Player(PlayerName, Piece, this));
                    return true;
                }
            }
            else
                throw new PlayerNameAlreadyExistsException();
            return false;
        }

        /// <summary>
        /// Returns <code>true</code> if the Game has at least two players, otherwise <code>false</code>
        /// </summary>
        /// <returns><code>true</code> if the game have enought players</returns>
        public bool HasPlayers()
        {
            return this.Players.Count >= 2;
        }

        /// <summary>
        /// Allow a player to throw the dices
        /// <remarks>Throw a new GoToJailException if the player add three doubles in row</remarks>
        /// </summary>
        public void ThrowDices()
        {
            this.Dice1.ThrowDice(Random);
            this.Dice2.ThrowDice(Random);
            Program.WriteLine("\nJet de dés : " + this.GetTotalDicesValue() + " (" + this.Dice1.GetValue() + "+" + this.Dice2.GetValue() + ")");
            if (this.IsDoubles())
                this.DoublesInRow++;
        }

        /// <summary>
        /// Return the total dices value
        /// </summary>
        /// <returns><code>int</code></returns>
        public int GetTotalDicesValue()
        {
            return this.Dice1.GetValue() + this.Dice2.GetValue();
        }

        /// <summary>
        /// Check if both dices have the same value
        /// </summary>
        /// <returns><code>true</code> if both dices have the same value</returns>
        public bool IsDoubles()
        {
            return this.Dice1.GetValue() == this.Dice2.GetValue();
        }

        public short GetDoublesInRow()
        {
            return this.DoublesInRow;
        }

        /// <summary>
        /// Return the current player
        /// </summary>
        /// <returns><code>Player</code></returns>
        public Player GetCurrentPlayer()
        {
            return this.CurrentPlayer;
        }

        /// <summary>
        /// Return the bank
        /// </summary>
        /// <returns><code>Bank</code></returns>
        public Bank GetBank()
        {
            return this.Bank;
        }

        /// <summary>
        /// Return the gameboard
        /// </summary>
        /// <returns><code>Gameboard</code></returns>
        public Gameboard GetGameboard()
        {
            return this.Gameboard;
        }

        /// <summary>
        /// Return the list of players
        /// </summary>
        /// <returns><code>Players</code></returns>
        public IList<Player> GetPlayers()
        {
            return this.Players;
        }

        /// <summary>
        /// Pick a random player in the list to begin the game
        /// </summary>
        public void BeginGame()
        {
            this.CurrentPlayer = this.Players[new Random().Next(0, this.Players.Count - 1)];
            DisplayCurrentPlayer();
        }

        /// <summary>
        /// Give round to the next player in the list of players
        /// </summary>
        public void SetNextPlayer()
        {
            this.CurrentPlayer = this.Players[(this.Players.IndexOf(this.CurrentPlayer) + 1) % this.Players.Count];
            this.DoublesInRow = 0;
            DisplayCurrentPlayer();
        }

        /// <summary>
        /// Display the current's player name
        /// </summary>
        private void DisplayCurrentPlayer()
        {
            //TODO uncomment: 
            Console.Clear();
            Program.WriteLine("C'est à " + CurrentPlayer.GetName().Colorize(ConsoleColor.White) + " de jouer");
            Program.WriteLine("Vous avez " + this.CurrentPlayer.GetCash().ToEuro(true));
            this.CurrentPlayer.DisplayBoxPropertyList();
        }

        /// <summary>
        /// Allow the current player to leave the game on purpose or because he's broken
        /// </summary>
        public void RemoveCurrentPlayer()
        {
            Player PlayerToRemove = this.CurrentPlayer;
            SetNextPlayer();
            this.Players.Remove(PlayerToRemove);
        }

        /// <summary>
        /// Computes and returns the list of players that have at least one property
        /// </summary>
        /// <returns>List of players with one or more property</returns>
        public IList<Player> GetPlayersWithFields()
        {
            return this.Players.Where(p => p.HasProperty()).ToList<Player>();
        }
    }
}
